﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Runtime.InteropServices;

namespace uam_fps_game.Scenes
{
    public class Terrain
    {

            int heightOfTerrain;
            int widthOfTerrain;

        // Plik "efektu", ktory mozna utozsamiac z "shaderem", nie jest to jednak dokladnie to samo.
        Effect myEffect;
        
        Texture2D texture;

        public Matrix WorldMatrix { get; set; }

        GraphicsDevice graphicsDevice;

        VertexPositionTexture[] vertices;
        int[] indices;

        public void LoadHeightData(Texture2D heightMap)
        {
            int heightmapWidth = (int)heightMap.Width;
            int heightmapHeight = (int)heightMap.Height;

            Color[] heightMapColors = new Color[heightmapWidth * heightmapHeight];
            heightMap.GetData(heightMapColors);

            for (int x = 0; x < heightOfTerrain; x++)
                for (int y = 0; y < widthOfTerrain; y++)
                {
                    int mapX = (int)((float)(x) * (float)(heightmapHeight) / (float)(heightOfTerrain));
                    int mapY = (int)((float)(y) * (float)(heightmapWidth) / (float)(widthOfTerrain)); ;
                    vertices[y + widthOfTerrain * x].Position.Y = heightMapColors[mapY + widthOfTerrain * mapX].R*0.4f;
                }
        }

        /// <summary>
        /// Generates terrain mesh.
        /// </summary>
        /// <param name="heightMapFileName">File with heightmap, that will be used to supply heights to verticecs</param>
        /// <param name="scale">X, Y, Z values of this parameter represent desired scaling of terrain in corresponding axes</param>
        public Terrain(string heightMapFileName, Vector3 scale, ContentManager content, GraphicsDevice device)
        {
            WorldMatrix = Matrix.Identity;

            graphicsDevice = device;

            Texture2D HeightTexture;

            // ladowanie tekstury
            texture = content.Load<Texture2D>("Textures\\szachownica");
            HeightTexture = content.Load<Texture2D>("Textures\\HeightMap");

            // ladowanie efektu i ustawienie aktualnej techniki
            myEffect = content.Load<Effect>("Shaders\\TerrainEffect").Clone();
            myEffect.CurrentTechnique = myEffect.Techniques["Technique1"];

            MyVertexPositionTexture.Init(device);


            // Tworzenie i wypelnianie tablicy wierzcholkow i indeksow

            heightOfTerrain = 256;
            widthOfTerrain = 256;

            /*if (widthOfTerrain * heightOfTerrain * 6 > 4294967295)
            {
                return;
            }*/


            vertices = new VertexPositionTexture[widthOfTerrain * heightOfTerrain];

            for (int i = 0; i < heightOfTerrain; i++)//generowanie wierzchołków terenu
            {
                for (int j = 0; j < widthOfTerrain; j++)
                {
                    vertices[j + widthOfTerrain * i] = new VertexPositionTexture(new Vector3(1*j, 10, 1*i), new Vector2((float)(i) / ((float)(heightOfTerrain) - 1), (float)(j) / ((float)(widthOfTerrain) - 1)));
                }
            }

            indices = new int[6 * heightOfTerrain * widthOfTerrain];//łączenie wierzchołków terenu krawędziami.

            for (int i = 0; i < heightOfTerrain - 1; i++)
            {
                for (int j = 0; j < widthOfTerrain - 1; j++)
                {
                    indices[6 * j + 6 * widthOfTerrain * i] = (int)(j + widthOfTerrain * i);
                    indices[6 * j + 6 * widthOfTerrain * i + 1] = (int)(j + 1 + widthOfTerrain * i);
                    indices[6 * j + 6 * widthOfTerrain * i + 2] = (int)(j + 1 + widthOfTerrain * (i + 1));

                    indices[6 * j + 6 * widthOfTerrain * i + 3] = (int)(j + widthOfTerrain * i);
                    indices[6 * j + 6 * widthOfTerrain * i + 4] = (int)(j + 1 + widthOfTerrain * (i + 1));
                    indices[6 * j + 6 * widthOfTerrain * i + 5] = (int)(j + widthOfTerrain * (i + 1));
                }
            }
            LoadHeightData(HeightTexture);
        }

        /// <summary>
        /// Renders terrain with given view and projection matrices, and with world matrix that is currently assigned to object
        /// </summary>
        /// <param name="view"></param>
        /// <param name="projection"></param>
        /// 
        [StructLayout(LayoutKind.Sequential)]
        public struct MyVertexPositionTexture
        {
            // Kolejne dwie linijki to w zasadzie format wierzcholka. Dalsze elementy nie zajmuja miejsca w pamieci w strukturze wierzcholka.
            public Vector3 position;
            public Vector2 texcoord;

            /*public MyVertexPositionTexture(float px, float py, float pz, float ts, float tt)
            {
                position.X = px;
                position.Y = py;
                position.Z = pz;
                texcoord.X = ts;
                texcoord.Y = tt;
                //VertexPositionTexture
            }*/

            // Statyczne pola pomocnicze i metoda inicjalizujaca

            public static VertexElement[] VertexElements;
            public static VertexDeclaration VertexDecl;
            public static int SizeInBytes { get; private set; }

            public static void Init(GraphicsDevice device)
            {
                VertexElements = new VertexElement[]
                { // Musi miec taki sam format jak cala struktura
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                    new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
                };

                VertexDecl = new VertexDeclaration(VertexElements);

                // Wielkosc wierzcholka: Vector3 - 3*4, Vector2 - 2*4 (4 - tyle bajtow w pamieci zajmuje float)
                SizeInBytes = 12 + 8;
            }
        }

        public void Render(Matrix view, Matrix projection)
        {
            // Ustawianie wartosci globalnych zmiennych shadera.
            myEffect.Parameters["World"].SetValue(WorldMatrix);
            myEffect.Parameters["View"].SetValue(view);
            myEffect.Parameters["Projection"].SetValue(projection);
            myEffect.Parameters["Texture"].SetValue(texture);

            graphicsDevice.DepthStencilState = DepthStencilState.Default;
            graphicsDevice.BlendState = BlendState.Opaque;

            // Dla kazdego "passa" w aktualnej technice - u nas jest tylko jeden pass,
            // wiec moznaby wykonac kod petli jedynie dla "myEffect.CurrentTechnique.Passes[0]"
            foreach (EffectPass pass in myEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                // Najwazniejsza czesc rysowania - tzw. Draw Call - polecenia rysowania dla karty
                graphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3, MyVertexPositionTexture.VertexDecl);
            }
        }
            
    }
}
